/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2011 The Boeing Company
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Author:  Tom Henderson <thomas.r.henderson@boeing.com>
 * 			Stephane Kamga <stepos01@gmail.com>
 */

#include "ns3/abort.h"
#include "ns3/node.h"
#include "lr-wpan-net-device.h"
#include "ns3/lr-wpan-mac.h"
#include "ns3/lr-wpan-phy.h"
#include "ns3/lr-wpan-csmaca.h"
#include "ns3/spectrum-channel.h"
#include "ns3/pointer.h"
#include "ns3/lr-wpan-associated-device.h"
#include  "ns3/lr-wpan-helper.h"

NS_LOG_COMPONENT_DEFINE ("LrWpanNetDevice");

namespace ns3 {
LrWpanHelper helper;

NS_OBJECT_ENSURE_REGISTERED (LrWpanNetDevice);

TypeId
LrWpanNetDevice::GetTypeId (void)
{
  static TypeId tid = TypeId ("ns3::LrWpanNetDevice")
    .SetParent<NetDevice> ()
    .AddConstructor<LrWpanNetDevice> ()
    .AddAttribute ("Channel", "The channel attached to this device",
                   PointerValue (),
                   MakePointerAccessor (&LrWpanNetDevice::DoGetChannel),
                   MakePointerChecker<SpectrumChannel> ())
    .AddAttribute ("Phy", "The PHY layer attached to this device.",
                   PointerValue (),
                   MakePointerAccessor (&LrWpanNetDevice::GetPhy,
                                        &LrWpanNetDevice::SetPhy),
                   MakePointerChecker<LrWpanPhy> ())
    .AddAttribute ("Mac", "The MAC layer attached to this device.",
                   PointerValue (),
                   MakePointerAccessor (&LrWpanNetDevice::GetMac,
                                        &LrWpanNetDevice::SetMac),
                   MakePointerChecker<LrWpanMac> ())
  ;
  return tid;
}

LrWpanNetDevice::LrWpanNetDevice ()
  : m_configComplete (false)
{
  NS_LOG_FUNCTION (this);
  m_mac = CreateObject<LrWpanMac> ();
  m_phy = CreateObject<LrWpanPhy> ();
  m_csmaca = CreateObject<LrWpanCsmaCa> ();
  CompleteConfig ();
}

LrWpanNetDevice::~LrWpanNetDevice ()
{
  NS_LOG_FUNCTION (this);
}


void
LrWpanNetDevice::DoDispose (void)
{
  NS_LOG_FUNCTION (this);
  m_mac->Dispose ();
  m_phy->Dispose ();
  m_csmaca->Dispose ();
  m_phy = 0;
  m_mac = 0;
  m_csmaca = 0;
  m_node = 0;
  // chain up.
  NetDevice::DoDispose ();


}

void
LrWpanNetDevice::DoStart (void)
{
  NS_LOG_FUNCTION (this);
  m_phy->Start ();
  m_mac->Start ();
  NetDevice::DoStart ();
}


void
LrWpanNetDevice::CompleteConfig (void)
{
  NS_LOG_FUNCTION (this);
  if (m_mac == 0
      || m_phy == 0
      || m_csmaca == 0
      || m_node == 0
      || m_configComplete)
    {
      return;
    }
  m_mac->SetPhy (m_phy);
  m_mac->SetCsmaCa (m_csmaca);
  m_csmaca->SetMac (m_mac);



  m_phy->SetMobility (m_node);
  Ptr<LrWpanErrorModel> model = CreateObject<LrWpanErrorModel> ();
  m_phy->SetErrorModel (model);

  m_phy->SetPdDataIndicationCallback (MakeCallback (&LrWpanMac::PdDataIndication, m_mac));
  m_phy->SetPdDataConfirmCallback (MakeCallback (&LrWpanMac::PdDataConfirm, m_mac));
  m_phy->SetPlmeEdConfirmCallback (MakeCallback (&LrWpanMac::PlmeEdConfirm, m_mac));
  m_phy->SetPlmeGetAttributeConfirmCallback (MakeCallback (&LrWpanMac::PlmeGetAttributeConfirm, m_mac));
  m_phy->SetPlmeSetTRXStateConfirmCallback (MakeCallback (&LrWpanMac::PlmeSetTRXStateConfirm, m_mac));
  m_phy->SetPlmeSetAttributeConfirmCallback (MakeCallback (&LrWpanMac::PlmeSetAttributeConfirm, m_mac));

  m_mac->setMlmeAssociateConfirmCallback(MakeCallback(&LrWpanNetDevice::MlmeAssociateConfirm,this));
  m_mac->setMlmeAssociateIndicationCallback(MakeCallback(&LrWpanNetDevice::MlmeAssociateIndication,this));
  m_mac->setMlmeStartConfirmCallback(MakeCallback(&LrWpanNetDevice::MlmeStartConfirm,this));
  m_mac->setMlmeGetConfirmCallback(MakeCallback(&LrWpanNetDevice::MlmeGetConfirm,this));
  m_mac->setMlmeCommStatusIndicationCallback(MakeCallback(&LrWpanNetDevice::MlmeCommStatusIndication,this));
  m_mac->setMlmeDisassociateConfirmCallback(MakeCallback(&LrWpanNetDevice::MlmeDisassociateConfirm,this));
  m_mac->setMlmeDisassociateIndicationCallback(MakeCallback(&LrWpanNetDevice::MlmeDisassociateIndication,this));
  m_mac->setMlmePollConfirmCallback(MakeCallback(&LrWpanNetDevice::MlmePollConfirm,this));
  m_mac->setMlmeScanConfirmCallback(MakeCallback(&LrWpanNetDevice::MlmeScanConfirm, this));
  m_mac->setMlmeOrphanIndicationCallback(MakeCallback(&LrWpanNetDevice::MlmeOrphanIndication,this));
  m_mac->setMlmeSetConfirmCallback(MakeCallback(&LrWpanNetDevice::MlmeSetConfirm,this));
  //todo add the mcps

  m_csmaca->SetLrWpanMacStateCallback (MakeCallback (&LrWpanMac::SetLrWpanMacState, m_mac));
  m_phy->SetPlmeCcaConfirmCallback (MakeCallback (&LrWpanCsmaCa::PlmeCcaConfirm, m_csmaca));

  //we set the CSMA value from the MAC PIB attributes
 /* MlmeGetRequestParams getParams;
  getParams.PIBattributeID.mac_attributeID = macMinBE;
  getParams.isMACattribute = true;
  m_mac->MlmeGetRequest(getParams);
  m_csmaca->setMacMinBE(m_PIBattribute.mac_attribute->macMinBE);
  m_csmaca->setMacMaxBE(m_PIBattribute.mac_attribute->macMaxBE);
  m_csmaca->setmacMaxCSMABackoffs(m_PIBattribute.mac_attribute->macMaxCSMABackoffs);
  m_csmaca->setUnitBackoffPeriod(LrWpanMac::aUnitBackoffPeriod);
  m_PIBattribute.mac_attribute->macBeaconOrder == 15 ? m_csmaca->setUnSlottedCsmaCa(): m_csmaca->setSlottedCsmaCa();
*/
  m_configComplete = true;
}
PIBattribute*
LrWpanNetDevice::GetPIBattributes(void)
{
	return &m_PIBattribute;
}

void
LrWpanNetDevice::SetMac (Ptr<LrWpanMac> mac)
{
  NS_LOG_FUNCTION (this);
  m_mac = mac;
  CompleteConfig ();
}

void
LrWpanNetDevice::SetPhy (Ptr<LrWpanPhy> phy)
{
  NS_LOG_FUNCTION (this);
  m_phy = phy;
  CompleteConfig ();
}

void
LrWpanNetDevice::SetCsmaCa (Ptr<LrWpanCsmaCa> csmaca)
{
  NS_LOG_FUNCTION (this);
  m_csmaca = csmaca;
  CompleteConfig ();
}

void
LrWpanNetDevice::SetChannel (Ptr<SpectrumChannel> channel)
{
  NS_LOG_FUNCTION (this << channel);
  m_phy->SetChannel (channel);
  channel->AddRx (m_phy);
  CompleteConfig ();
}

Ptr<LrWpanMac>
LrWpanNetDevice::GetMac (void) const
{
  NS_LOG_FUNCTION (this);
  return m_mac;
}

Ptr<LrWpanPhy>
LrWpanNetDevice::GetPhy (void) const
{
  NS_LOG_FUNCTION (this);
  return m_phy;
}

Ptr<LrWpanCsmaCa>
LrWpanNetDevice::GetCsmaCa (void) const
{
  NS_LOG_FUNCTION (this);
  return m_csmaca;
}
void
LrWpanNetDevice::SetIfIndex (const uint32_t index)
{
  NS_LOG_FUNCTION (this << index);
  m_ifIndex = index;
}

uint32_t
LrWpanNetDevice::GetIfIndex (void) const
{
  NS_LOG_FUNCTION (this);
  return m_ifIndex;
}

Ptr<Channel>
LrWpanNetDevice::GetChannel (void) const
{
  NS_LOG_FUNCTION (this);
  return m_phy->GetChannel ();
}

void
LrWpanNetDevice::LinkUp (void)
{
  NS_LOG_FUNCTION (this);
  m_linkUp = true;
  m_linkChanges ();
}
void
LrWpanNetDevice::LinkDown (void)
{
  NS_LOG_FUNCTION (this);
  m_linkUp = false;
  m_linkChanges ();
}


Ptr<SpectrumChannel>
LrWpanNetDevice::DoGetChannel (void) const
{
  NS_LOG_FUNCTION (this);
  return m_phy->GetChannel ();
}
void
LrWpanNetDevice::SetAddress (Address address)
{
  NS_LOG_FUNCTION (this);
  m_mac->SetExtendedAddress (Mac64Address::ConvertFrom (address));
}
Address
LrWpanNetDevice::GetAddress (void) const
{

	//use the MLME-Get instead
	return Address();
}

bool
LrWpanNetDevice::SetMtu (const uint16_t mtu)
{
  NS_ABORT_MSG ("Unsupported");
  return false;
}

uint16_t
LrWpanNetDevice::GetMtu (void) const
{
  NS_ABORT_MSG ("Unsupported");
  return 0;
}

bool
LrWpanNetDevice::IsLinkUp (void) const
{
  NS_LOG_FUNCTION (this);
  return m_phy != 0 && m_linkUp;
}

void
LrWpanNetDevice::AddLinkChangeCallback (Callback<void> callback)
{
  NS_LOG_FUNCTION (this);
  m_linkChanges.ConnectWithoutContext (callback);
}

bool
LrWpanNetDevice::IsBroadcast (void) const
{
  NS_LOG_FUNCTION (this);
  return true;
}

Address
LrWpanNetDevice::GetBroadcast (void) const
{
  NS_ABORT_MSG ("Unsupported; add me");
  return Address ();
}

bool
LrWpanNetDevice::IsMulticast (void) const
{
  NS_LOG_FUNCTION (this);
  return false;
}

Address
LrWpanNetDevice::GetMulticast (Ipv4Address multicastGroup) const
{
  NS_ABORT_MSG ("Unsupported");
  return Address ();
}

Address
LrWpanNetDevice::GetMulticast (Ipv6Address addr) const
{
  NS_ABORT_MSG ("Unsupported");
  return Address ();
}

bool
LrWpanNetDevice::IsBridge (void) const
{
  NS_LOG_FUNCTION (this);
  return false;
}

bool
LrWpanNetDevice::IsPointToPoint (void) const
{
  NS_LOG_FUNCTION (this);
  return false;
}

bool
LrWpanNetDevice::Send (Ptr<Packet> packet, const Address& dest, uint16_t protocolNumber)
{
  // This method basically assumes an 802.3-compliant device, but a raw
  // 802.15.4 device does not have an ethertype, and requires specific
  // McpsDataRequest parameters.
  // For further study:  how to support these methods somehow, such as
  // inventing a fake ethertype and packet tag for McpsDataRequest
  NS_ABORT_MSG ("Unsupported; use McpsDataRequest instead");
  return false;
}

bool
LrWpanNetDevice::SendFrom (Ptr<Packet> packet, const Address& source, const Address& dest, uint16_t protocolNumber)
{
  NS_ABORT_MSG ("Unsupported; use McpsDataRequest instead");
  return false;
}

Ptr<Node>
LrWpanNetDevice::GetNode (void) const
{
  NS_LOG_FUNCTION (this);
  return m_node;
}

void
LrWpanNetDevice::SetNode (Ptr<Node> node)
{
  NS_LOG_FUNCTION (this);
  m_node = node;
  CompleteConfig ();
}

bool
LrWpanNetDevice::NeedsArp (void) const
{
  NS_ABORT_MSG ("Unsupported");
  return false;
}

void
LrWpanNetDevice::SetReceiveCallback (ReceiveCallback cb)
{
  // This method basically assumes an 802.3-compliant device, but a raw
  // 802.15.4 device does not have an ethertype, and requires specific
  // McpsDataIndication parameters.
  // For further study:  how to support these methods somehow, such as
  // inventing a fake ethertype and packet tag for McpsDataRequest
  NS_LOG_WARN ("Unsupported; use LrWpan MAC APIs instead");
}

void
LrWpanNetDevice::SetPromiscReceiveCallback (PromiscReceiveCallback cb)
{
  // This method basically assumes an 802.3-compliant device, but a raw
  // 802.15.4 device does not have an ethertype, and requires specific
  // McpsDataIndication parameters.
  // For further study:  how to support these methods somehow, such as
  // inventing a fake ethertype and packet tag for McpsDataRequest
  NS_LOG_WARN ("Unsupported; use LrWpan MAC APIs instead");
}

bool
LrWpanNetDevice::SupportsSendFrom (void) const
{
	NS_LOG_FUNCTION_NOARGS ();
	return false;
}
//**********************************MLME**************************************
void
LrWpanNetDevice::MlmePollConfirm(MlmePollConfirmParams params)
{
	NS_LOG_FUNCTION(this << helper.LrWpanMacStatusResultPrinter(params.status));
}
void
LrWpanNetDevice::MlmeStartConfirm(MlmeStartConfirmparams params)
{
	NS_LOG_FUNCTION(this << helper.LrWpanMacStatusResultPrinter(params.status));
}
void
LrWpanNetDevice::MlmeCommStatusIndication(MlmeCommStatusIndicationParams params)
{
		NS_LOG_FUNCTION(this << helper.LrWpanMacStatusResultPrinter(params.status));
}
void
LrWpanNetDevice::MlmeGetConfirm(MlmeGetConfirmParams params)
{
	NS_LOG_FUNCTION(this << params.status);
	if(params.status == IEEE_802_15_4_SUCCESS)
	{
		if(params.isMACattribute)
			m_PIBattribute.mac_attribute = params.PIBattribute.mac_attribute;
		else
			m_PIBattribute.phy_attribute = params.PIBattribute.phy_attribute;
	}
}
void
LrWpanNetDevice::MlmeSetConfirm(MlmeSetConfirmParams params)
{
	NS_LOG_FUNCTION(this << params.status);
}
void
LrWpanNetDevice::MlmeRXEnableConfirm(MlmeRXEnableConfirmParams params)
{
	NS_LOG_FUNCTION(this << params.status);
}
void
LrWpanNetDevice::MlmeAssociateIndication(MlmeAssociateIndicationParams params)
{
	NS_LOG_FUNCTION(this);
	//we verify the macAssociation Permit attributes of the coordinator
	MlmeGetRequestParams input;
	input.isMACattribute = true;
	input.PIBattributeID.mac_attributeID = macAssociationPermit;
	m_mac->MlmeGetRequest(input);
	MlmeAssociateResponseParams responseParams;
	if(!m_PIBattribute.mac_attribute->macAssociationPermit)
	{
		NS_LOG_FUNCTION("The coordinator does not allow association");
		responseParams.AssocShortAddress = Mac16Address("ff:ff");
		responseParams.DeviceAddress = params.DeviceAddress;
		responseParams.SecurityLevel = params.SecurityLevel;
		responseParams.status = IEEE_802_15_4_PAN_ACCESS_DENIED;
	}
	else{
		NS_ASSERT_MSG(m_mac->GetObject<LrWpanAssociatedDevice>(),"LrwpanAssociatedDevice not intialized");
		m_mac->GetObject<LrWpanAssociatedDevice>()->removeAssociateDevice(params.DeviceAddress,EXT_ADDR);
		LrWpanMACResultStatus state = IEEE_802_15_4_ASSOCIATION_SUCCESSFULL;
		Mac16Address add =  Mac16Address::Allocate();
		if(add == Mac16Address("ff:ff"))
			state=IEEE_802_15_4_PAN_AT_CAPACITY;
		else{
			if(((params.CapabilityInformation)&(0x01)) == 0x00)   //the device don't wish the coordinator to allocate a 16-bit short address
				add = Mac16Address("ff:fe");
		}
		responseParams.AssocShortAddress = add;
		responseParams.DeviceAddress = params.DeviceAddress;
		responseParams.SecurityLevel = params.SecurityLevel;
		responseParams.status = state;
	}
	m_mac->MlmeAssociateResponse(responseParams);
}
void
LrWpanNetDevice::MlmeAssociateConfirm(MlmeAssociateConfirmParams params)
{
	NS_LOG_FUNCTION(this);
}
void
LrWpanNetDevice::MlmeDisassociateConfirm(MlmeDisassociateConfirmParams params)
{
	NS_LOG_FUNCTION(this << helper.LrWpanMacStatusResultPrinter(params.status));
}
void
LrWpanNetDevice::MlmeDisassociateIndication(MlmeDisassociateIndicationParams params)
{
	NS_LOG_FUNCTION(this);
}
void
LrWpanNetDevice::MlmeScanConfirm(MlmeScanConfirmParams params)
{
	NS_LOG_FUNCTION(this << helper.LrWpanMacStatusResultPrinter(params.status));
}
void
LrWpanNetDevice::MlmeOrphanIndication(MlmeOrphanIndicationParams params)
{
	NS_LOG_FUNCTION(this);
	if(GetMac()->GetObject<LrWpanAssociatedDevice>())
	{
		if(GetMac()->GetObject<LrWpanAssociatedDevice>()->searchAssociateDevice(params.OrphanAddress.operator ns3::Address(),EXT_ADDR));
		{
			MlmeOrphanResponseParams returnParams;
			returnParams.AssociatedMember = true;
			returnParams.ShortAddress = Mac16Address::ConvertFrom(GetMac()->GetObject<LrWpanAssociatedDevice>()->getAssociateDevice(params.OrphanAddress.operator ns3::Address(),EXT_ADDR));
			returnParams.SecurityLevel = params.SecurityLevel;
			returnParams.OrphanAddress = params.OrphanAddress;
			GetMac()->MlmeOrphanResponse(returnParams);
		}
	}
	else
	{
		NS_LOG_ERROR("the associatedDevice Object is not initialized");
	}
}
} // namespace ns3
